/*  
# Copyright 2008 Mike Chirico mchirico@gmail.com
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#


  If you're using postfix,  in the file /etc/postfix/main.cf
  make the following changes:

         smtpd_tls_loglevel = 2

  When this client connect to your postfix server, you'll
  want to see if there are any problems.

  Don't forget to restart postfix.



Similiar Products:

  POPFile
   http://popfile.sourceforge.net/



Notes:


      BIO_new_socket:
         sbio=BIO_new_socket(s,BIO_NOCLOSE);



    References:
        http://www.faqs.org/rfcs/rfc2821.html

     bcc - blind carbon copy
        http://www.faqs.org/rfcs/rfc822.html

     OpenSSL Command-Line HOWTO
        http://www.madboa.com/geek/openssl/

     OpenSSL Programming examples
        http://www.opensslbook.com/code.html

     TLS
        http://www.faqs.org/rfcs/rfc3207.html

     IMAP
        http://www.faqs.org/rfcs/rfc3501.html

     VERP
        http://cr.yp.to/proto/verp.txt



You must encode the password using Base64. This is put in the
file password_smtp_64 with no return after the encoded login username.

perl -MMIME::Base64 -e     'print encode_base64("\000dev.mchirico\@gmail.com\000password");' > password_smtp_64


Compile:

    gcc gmail.c -lssl -o gmail


*/

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>

#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <getopt.h>

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/x509v3.h>

#ifndef _VERSION_H
#define _VERSION_H

#define VERSION	"0.0.1"

#endif				/* _VERSION_H */



#define SA      struct sockaddr
#define MAXLINE 8192
#define MAXSUB  2048
#define MAXBUF  2048
#define READLN  512
#define LISTENQ         1024
extern int h_errno;





static int verify_callback(int preverify_ok, X509_STORE_CTX * ctx)
{

	typedef struct {
		int verbose_mode;
		int verify_depth;
		int always_continue;
	} mydata_t;

	char buf[256];
	X509 *err_cert;
	int err, depth, mydata_index = 0;
	SSL *ssl;
	mydata_t *mydata;



	err_cert = X509_STORE_CTX_get_current_cert(ctx);
	depth = X509_STORE_CTX_get_error_depth(ctx);
	ssl =
	    X509_STORE_CTX_get_ex_data(ctx,
				       SSL_get_ex_data_X509_STORE_CTX_idx
				       ());
	err = X509_STORE_CTX_get_error(ctx);
	fprintf(stderr, "  err %i:%s\n", err,
		X509_verify_cert_error_string(err));

	mydata = SSL_get_ex_data(ssl, mydata_index);

	/*
	   X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256);
	   or */
	X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
	printf("issuer= %s\n", buf);




	err = X509_STORE_CTX_get_error(ctx);
	fprintf(stderr, "X509_verify_cert_error_string=(%s)\n",
		X509_verify_cert_error_string(err));

	/* Return 1 accept the connection. 
	   Return 0 reject the connection 

	   This isn't the only place this decision is made to continue.

	 */

	preverify_ok = 1;
	return preverify_ok;
}


SSL_CTX *setup_client_ctx(void)
{
	SSL_CTX *ctx;
	ctx = SSL_CTX_new(SSLv23_method());
	SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
	SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2);
	return ctx;
}


int get_password(char *file, char *password, int length)
{
	FILE *fp;
	char *line = NULL;
	ssize_t n;
	size_t len;
	fp = fopen(file, "r");
	if (fp == NULL) {
		fprintf(stderr, "Can't read password file\n");
		return 0;
	}
	len = (size_t) length;
	n = getline(&line, &len, fp);

	snprintf(password, MAXLINE, "%s", line);


	if (strlen(password) > 0)
		if (password[strlen(password) - 1] == '\n')
			password[strlen(password) - 1] = '\0';

	if (line)
		free(line);

	return 1;

}


int socket_write(int sockfd, char *_sendline)
{
	int n = 0;
	char sendline[MAXLINE + 1];
	snprintf(sendline, MAXLINE, _sendline);
	n = write(sockfd, sendline, strlen(sendline));
	return n;

}

int SSL_socket_write(SSL * ssl, char *_sendline)
{
	int n = 0;


	char sendline[MAXLINE + 1];
	snprintf(sendline, MAXLINE, _sendline);


	n = SSL_write(ssl, sendline, (int) strlen(sendline));
	return n;

}


char *socket_read(int sockfd, char *recvline)
{
	int n;

	n = read(sockfd, recvline, MAXLINE);
	recvline[n] = '\0';
	return recvline;
}

char *SSL_socket_read(SSL * ssl, char *recvline)
{
	int n;

	n = SSL_read(ssl, recvline, MAXLINE);
	recvline[n] = '\0';
	return recvline;
}




static long addmem(char **buf, long size)
{
#ifndef MULT_START
#define MULT_START 16384
#endif

#ifndef MULT_INCREMENT
#define MULT_INCREMENT 2
#endif

#ifndef INIT_SIZE
#define INIT_SIZE      1024
#endif

	size_t memindx = MULT_START;
	size_t memnext;

	char *tbuf;

	memnext = (size > 0) ? size + memindx : INIT_SIZE;
	memindx = memindx * MULT_INCREMENT;

	tbuf = realloc(*buf, memnext);
	if (tbuf == NULL) {
		fprintf(stderr, "Can't allocate memory in addmem\n");
		return size;
	} else {
		*buf = tbuf;
		return memnext;
	}

}

static char *readin()
{
	int fd, fd2, n;
	long len = 0, totread = 0;
	char *buf = NULL, *pbuf = NULL;

	int numread = 0;

	if ((fd = fileno(stdin)) == -1) {
		fprintf(stderr, "Can't open stdin: %s\n", strerror(errno));
		return NULL;
	}
	/* This is for writing a copy of data
	   if ((fd2 = open(file, O_RDWR | O_CREAT, 0600)) == -1) {
	   fprintf(stderr, "Can't open data: %s\n", strerror(errno));
	   return NULL;
	   }
	 */



	while (len - totread - 1 < 1024)
		len = addmem(&buf, len);
	pbuf = buf;



	while ((n = read(fd, pbuf, 1024)) > 0) {
		totread += n;
		pbuf[n] = '\0';	// This is for printing test
		while (len - totread - 1 < 1024)
			len = addmem(&buf, len);

		pbuf = &buf[totread];

		numread += 1;	// just for stats
	}
	/*
	   write(fd2, buf, totread);
	   close(fd2);
	 */
	close(fd);

	return buf;

}





static void base64(char *str, size_t strb, char **p, size_t size)
{

	BIO *bio, *b64, *mem, *a, *b, *cipher;
	char buf[MAXBUF];

	char *t = (char *) malloc(sizeof(char) * size);
	int n = 0, total = 0;


	a = BIO_new(BIO_s_bio());
	BIO_set_write_buf_size(a, MAXBUF);
	b = BIO_new(BIO_s_bio());
	BIO_set_write_buf_size(b, MAXBUF);
	BIO_make_bio_pair(a, b);


	b64 = BIO_new(BIO_f_base64());
	BIO_push(b64, a);

	n = BIO_write(b64, str, strb);


	BIO_flush(b64);
	BIO_pop(b64);

	n = BIO_pending(b);



	while ((n = BIO_read(b, buf + total, READLN)) > 0) {
		total += n;
	}

	if (total > 0)
		buf[total - 1] = '\0';
	else
		buf[0] = '\0';

	n = BIO_pending(b);




	snprintf(t, size, "%s", buf);

	*p = t;



	BIO_destroy_bio_pair(a);
	BIO_free_all(a);
	BIO_free_all(b);


}

/* 
   This routine puts in the null values:

     \0john.chirico@gmail.com\0Pa33word\0

   Plus, it does a little error checking.

*/
char *encode_user_passd(char *u, char *p)
{
	char *t0, *t0b;
	char *t1, *t1b;


	size_t strlu = 0;
	size_t strlp = 0;


	strlu = strlen(u);
	strlp = strlen(p);
	if (strlu != 0) {	/* This could probably be 4 */
		t0 = (char *) malloc(sizeof(char) * (strlu + strlp + 10));
		if (t0 == NULL) {
			return NULL;
		}
	} else {
		return NULL;
	}

	t0b = t0;

	*t0++ = '\0';
	t1 = u;
	while (*t0++ = *t1++);

	t1 = p;
	while (*t0++ = *t1++);

	char *sp;

	int i = 1 + strlu + 1 + strlp + 1;
	base64(t0b, i, &sp, MAXSUB);

	free(t0b);

	return sp;

}

static int chrchk(char c)
{
	const char *s = " \n\r,\\\t|{}[]/?;:";
	while (*s) {
		if (*s == c)
			return 1;
		s++;
	}
	return 0;
}


void free_array(char ***t)
{
	char **p = *t;
	char **op = p;

	if (p == NULL)
		return;

	while (*p != NULL) {
		free(*p);
		++p;
	}
	free(op);
}



static char **build_array(char *s)
{
	char **argv;
	char *p;
	char *t;
	char *k;
	size_t i = 0, j = 0, index = 0;

	/* We won't have more than strlen(s) in our array */
	if (strlen(s) <= 0)
		return NULL;
	argv = malloc(strlen(s) * sizeof(char *));
	p = s;
	t = p;
	while (*p != '\0') {
		if (chrchk(*p)) {
			if (i > j) {
				k = (char *) malloc(sizeof(char *) *
						    (i - j) + 1);
				strncpy(k, t, (int) (i - j));
				k[(i - j)] = '\0';

				argv[index++] = k;
				argv[index] = NULL;
				j = i;
			}
			while (chrchk(*p)) {
				++i;
				++j;
				++p;
			}
			t = p;
		}

		if (*p != '\0') {
			++i;
			p++;
		}
	}
	/* This is the last string, if not terminated by \0 */
	if (i > j) {
		k = (char *) malloc(sizeof(char *) * (i - j) + 1);
		strncpy(k, t, (int) (i - j));
		k[(i - j) + 1] = '\0';
		argv[index++] = k;
		argv[index] = NULL;
	}

	return argv;
}



int mygetopt(int argc, char **argv, char *uemail, char *passwd, char *subj,
	     char *auth, char *to, char ***tp, char *smtps)
{


	char file[MAXBUF];

	uemail[0] = '\0';
	passwd[0] = '\0';
	subj[0] = '\0';
	file[0] = '\0';
	to[0] = '\0';

	char *p = 0;


	snprintf(smtps, MAXBUF, "%s", "smtp.gmail.com");




	p = getenv("HOME");
	if (p != NULL)
		snprintf(file, MAXBUF, "%s/%s", p, ".gmailrc");
	else
		snprintf(file, MAXBUF, "%s", ".gmailrc");


	int c = 0;
	int flags = 0;

	while (1) {

		int option_index = 0;
		static struct option long_options[] = {
			{"help", 0, 0, 'h'},
			{"username", 1, 0, 'u'},
			{"password", 1, 0, 'p'},
			{"version", 0, 0, 'v'},
			{"subject", 1, 0, 's'},
			{"file", 1, 0, 'f'},
			{"mailserver", 1, 0, 't'},
			{0, 0, 0, 0}
		};

		c = getopt_long(argc, argv, "hu:p:vs:f:t:",
				long_options, &option_index);
		if (c == -1)
			break;

		switch (c) {
		case 0:
			printf("option %s",
			       long_options[option_index].name);
			if (optarg)
				printf(" with arg %s", optarg);
			printf("\n");
			break;

		case 'h':
			flags |= 0x1;
			printf
			    ("gmail -s <subject> -u <user_name> -p <password>  toaddress\n"
			     "Example:  ./gmail -s 'Test Msg'  -u billybob@gmail.com -p Pa33word dev.mchirico@gmail.com\n");
			exit(0);
			break;

		case 'u':
			flags |= 0x2;
			snprintf(uemail, MAXBUF, "%s", optarg);
			break;

		case 'p':
			flags |= 0x4;
			snprintf(passwd, MAXBUF, "%s", optarg);
			break;

		case 'v':
			flags |= 0x8;
			printf
			    ("Copyright (C) Apache License, Version 2.0 by Mike Chirico <mchirico@gmail.com>\n"
			     "version %s \n" "  try --help\n" "Download:\n"
			     "  <need to fill in>\n" "Subversion:\n"
			     "  svn checkout http://google-c-tools.googlecode.com/svn/trunk google-c-tools\n"
			     "Source:\n"
			     "  http://google-c-tools.googlecode.com/svn/trunk/src/gmail.c\n\n",
			     VERSION);

			exit(0);
			break;

		case 's':
			flags |= 0x10;
			snprintf(subj, MAXBUF, "%s", optarg);
			break;

		case 'f':
			flags |= 0x40;
			snprintf(file, MAXBUF, "%s", optarg);
			break;

		case 't':
			flags |= 0x100;
			snprintf(smtps, MAXBUF, "%s", optarg);
			break;


		case '?':
			break;

		default:
			printf
			    ("?? getopt returned character code 0%o ??\n",
			     c);
		}
	}

	if (optind < argc) {
		/* printf("non-option ARGV-elements: "); */
		size_t i = MAXBUF - 1;
		while (optind < argc) {
			i = i - strlen(argv[optind]);
			strncat(to, argv[optind], i);
			--i;
			strncat(to, " ", i);
			--i;
			optind++;
		}
		*tp = build_array(to);
		/* printf("%s\n",to); */
	}

	if (flags & 0x1) {
		printf("h flag\n");
	}
	if (~flags & 0x06) {
		fprintf(stderr,
			"This program generates a Google auth token\n"
			"from a gmail account name and password\n"
			"Usage:\n\tgmail -u <email> -p <password>  -s <subj ah|cl|wise>\n"
			"Example:\n\tgmail -u zchirico@gmail.com -p Pa22word -s 'Example Subj'  jchirico@gmail.com\n");
		exit(2);
	}



	p = encode_user_passd(uemail, passwd);
	if (p != NULL) {
		snprintf(auth, MAXBUF, "%s", p);
		free(p);
		return 1;
	}

	return 0;
}





int main(int argc, char **argv)
{
	char uemail[MAXBUF];
	char passwd[MAXBUF];
	char subj[MAXBUF];
	char auth[MAXBUF];
	char envlpe[MAXLINE];
	char to[MAXBUF];
	char smtps[MAXBUF];

	char *r;
	char **p;
	char **hp;

	if (mygetopt(argc, argv, uemail, passwd, subj, auth, to, &p, smtps)
	    == 0) {
		return 0;
	}

	r = readin();

	hp = p;
	to[0] = '\0';		/* Needed for append */
	int to_sz = MAXBUF - 1;
	while (*p != NULL && p != NULL) {
		to_sz -= 5;
		strncat(to, "To: ", to_sz);
		to_sz -= strlen(*p);
		strncat(to, *p, to_sz);
		to_sz -= 4;
		strncat(to, " \r\n", to_sz);
		++p;
	}


	snprintf(envlpe, MAXLINE,
		 "DATA\r\nFrom: %s\r\n"
		 "%s" "subject: %s\r\n\r\n", uemail, to, subj);

	//      return 0;               /* debugging exit now */

	BIO *sbio;
	SSL *ssl;
	SSL_CTX *ctx;
	int ret = 0;


	SSL_library_init();
	RAND_load_file("/dev/urandom", 1024);

	ctx = setup_client_ctx();


	char sendline[MAXLINE], recvline[MAXLINE];


	/* Now doing standard connect stuff */
	int sockfd;
	struct sockaddr_in servaddr;

	char **pptr;
	//********** You can change. Put any values here *******
	// char *smtps = "smtp.gmail.com";
	//*******************************************************
	fprintf(stderr, "smtps=%s\n", smtps);

	char str[50];
	struct hostent *hptr;
	if ((hptr = gethostbyname(smtps)) == NULL) {
		fprintf(stderr, " gethostbyname error for host: %s: %s",
			smtps, hstrerror(h_errno));
		exit(1);
	}
	printf("hostname: %s\n", hptr->h_name);
	if (hptr->h_addrtype == AF_INET
	    && (pptr = hptr->h_addr_list) != NULL) {
		printf("address: %s\n",
		       inet_ntop(hptr->h_addrtype, *pptr, str,
				 sizeof(str)));
	} else {
		fprintf(stderr, "Error call inet_ntop \n");
	}

	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;

	servaddr.sin_port = htons(25);
	inet_pton(AF_INET, str, &servaddr.sin_addr);

	connect(sockfd, (SA *) & servaddr, sizeof(servaddr));


	fprintf(stderr, "%s\n", socket_read(sockfd, recvline));
	socket_write(sockfd, "EHLO squeezel.com\r\n");
	fprintf(stderr, "ehlo response:%s<-\n",
		socket_read(sockfd, recvline));


	socket_write(sockfd, "STARTTLS\r\n");
	fprintf(stderr, "response to starttls:%s<-\n",
		socket_read(sockfd, recvline));


	sbio = BIO_new_socket(sockfd, BIO_NOCLOSE);
	ssl = SSL_new(ctx);
	SSL_set_bio(ssl, sbio, sbio);
	SSL_set_connect_state(ssl);



	/* Circuit Breaker - NO encrypted connection */
	if (SSL_do_handshake(ssl) == 1) {
		fprintf(stderr, "SSL_do_handshake(ssl)=%d\n", 1);
	} else {
		fprintf(stderr, "SSL handshake error= %d\n",
			SSL_get_error(ssl, ret));
		fprintf(stderr,
			"Cannot establish an encrypted connection\nExiting\n\n");
		return -1;
	}


	SSL_socket_write(ssl, "EHLO squeezel.com\r\n");
	fprintf(stderr, "%s", SSL_socket_read(ssl, recvline));


	snprintf(sendline, MAXLINE, "AUTH PLAIN %s\r\n", auth);



	SSL_socket_write(ssl, sendline);
	fprintf(stderr, "%s", SSL_socket_read(ssl, recvline));

	/*
	   SSL_socket_write(ssl, "MAIL FROM: <dev.mchirico@gmail.com>\r\n");
	   fprintf(stderr, "2:%s", SSL_socket_read(ssl, recvline));
	 */

	SSL_socket_write(ssl, "MAIL FROM: <");
	SSL_socket_write(ssl, uemail);
	SSL_socket_write(ssl, ">\r\n");
	fprintf(stderr, "2:%s", SSL_socket_read(ssl, recvline));


	p = hp;
	while (*p != NULL && p != NULL) {
		SSL_socket_write(ssl, "RCPT TO: <");
		SSL_socket_write(ssl, *p);
		SSL_socket_write(ssl, ">\r\n");
		++p;
		fprintf(stderr, "3:%s", SSL_socket_read(ssl, recvline));
	}


	/*
	   SSL_socket_write(ssl,
	   "DATA\r\nFrom: dev.mchirico@gmail.com\r\n"
	   "To: mchirico@cwxstat.org\r\n"
	   "To: Souptonuts@googlegroups.com\r\n"
	   "subject: Example using tlssmtp.c\r\n"
	   "\r\nTest Send from tlssmtp.c" "\r\n.\r\n");
	 */

	SSL_socket_write(ssl, envlpe);
	fprintf(stderr, "*:%s", SSL_socket_read(ssl, recvline));
	SSL_socket_write(ssl, r);
	SSL_socket_write(ssl, "\r\n.\r\n");






	fprintf(stderr, "5:%s", SSL_socket_read(ssl, recvline));

	SSL_shutdown(ssl);
	SSL_clear(ssl);

	fprintf(stderr, "SSL Connection closed\n");


	SSL_free(ssl);
	SSL_CTX_free(ctx);

	free(r);
	free_array(&hp);
	return 0;

}
